from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, status, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.responses import FileResponse
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from datetime import datetime, timedelta
import os
import shutil
from typing import List, Optional, Union
from passlib.hash import bcrypt
from jose import jwt as jose_jwt
import jwt
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import func
from knowledge_graph import (
    load_knowledge,
    query_knowledge,
    process_document,
    extract_and_store_knowledge,
    graph_db, knowledge_agent, get_question_category,think_user_ask
)
from sqllite_pre import Base,get_db
from search_history import (SearchHistory, get_user_category_counts, get_search_history,
                            get_user_search_contents, SearchCategoryResponse, add_search_record,
                            delete_search_history, get_other_search_random)
from users import (User, UserResponse, PasswordForm, UserCreate, Token)
from feedback import (get_feedback_records, handle_feedback, delete_feedback, add_feedback, AddFeedback ,Feedback, FeedbackResponse)
from file import (add_file_record,delete_file_record,get_user_file_records )

# JWT配置
SECRET_KEY = "your-secret-key"  # 在生产环境中应该使用环境变量
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

app = FastAPI()

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

#获取登录用户信息
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    print('token', token)
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )

    username = verify_token(token)
    if username is None:
        raise credentials_exception

    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception

    return user


@app.get("/users/me", response_model=UserResponse)
async def get_current_user_info(current_user: User = Depends(get_current_user)):
    return current_user

#管理员获取用户列表
@app.get("/users/userList")
async def get_user_list(db: Session = Depends(get_db)):
    users = db.query(User).all()
    return {"data": users}

#管理员删除用户
@app.delete("/users/delete/{user_id}")
async def delete_user(user_id: int, db: Session = Depends(get_db)):
    # 删除用户
    db.query(User).filter_by(id=user_id).delete()
    db.commit()
    return {"message": "用户删除成功"}

#用户修改密码
@app.post("/users/change-password")
async def change_password(passwordform :PasswordForm, db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    print('修改的密码',passwordform.password)
    hashed_password = User.get_password_hash(passwordform.password)
    db.query(User).filter_by(id = current_user.id).update({'hashed_password' : hashed_password })
    db.commit()
    return {"message": "修改密码成功"}



#返回搜索历史各个分类次数数据
@app.get("/api/searchHistory", response_model=SearchCategoryResponse)
async def get_user_search_categories(db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    search_category = get_user_category_counts(db,current_user.id)
    return {"data": search_category}

#返回用户搜索记录列表
@app.get("/api/searchList")
async def get_user_search_list(db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    list = get_search_history(db,current_user.id)
    return {"data": list}

#返回所有搜索记录列表
@app.get("/api/allSearchList")
async def get_search_list(db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    list = get_search_history(db, -1)
    return {"data": list}

# 删除搜索记录的接口
@app.delete("/api/search/{record_id}")
async def delete_search_record(
    record_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    result = delete_search_history(db, record_id, current_user)
    return result

#返回其他用户搜索的随机的问题
@app.get("/api/allSearchList/other")
async def get_other_search(db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    result =  get_other_search_random(db, current_user.id)
    return {"data": result}

#用户可能想知道
@app.get("/api/recommendKnow")
async def recommend_user_know(db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    search_histories = await get_user_search_contents(db,current_user.id)
    print('搜索记录',repr(search_histories))
    think_content = await think_user_ask(repr(search_histories))
    print('推荐结果：',think_content)
    return {"data": think_content}

#获取反馈列表
@app.get("/api/feedback/list")
async def get_feedback_list(db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    result = get_feedback_records(db, -1)
    return {"data": result}

#用户获取反馈列表
@app.get("/api/feedback/user-list")
async def get_feedback_user_list(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    result = get_feedback_records(db, current_user.id)
    return {"data": result}

#新增反馈
@app.post("/api/feedback/add")
async def user_add_feedback(form: AddFeedback, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    new_feedback = add_feedback(
        db,
        current_user.id,
        current_user.username,
        current_user.email,
        form.subject,
        form.content
    )
    return {"message": "反馈提交成功"}

#删除反馈
@app.delete("/api/feedback/delete/{id}")
async def admin_delete_feedback(id: int, db: Session = Depends(get_db)):
    judge = delete_feedback(db, id)
    if judge:
        return {"message": "删除成功"}
    else:
        return {"message": "删除失败"}

#受理反馈
@app.get("/api/feedback/handler/{id}")
async def handler_feedback(id: int, db: Session = Depends(get_db)):
    handle_feedback(db, id)
    return {"message": "受理完成"}

#用户获取自己上传的文件列表
@app.get("/api/file/file-list")
async def get_user_files(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    result = get_user_file_records(db, current_user.id)
    # 转换为字典列表
    file_list = [
        {
            "id": file.id,
            "user_id": file.user_id,
            "username": file.username,
            "filename": file.filename
        }
        for file in result
    ]

    # 打印文件列表内容
    print('文件列表', file_list)
    return {"data": result}

# JWT相关函数
def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jose_jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str):
    try:
        payload = jose_jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            return None
        return username
    except jose_jwt.JWTError:
        return None



# 认证相关路由
@app.post("/register", response_model=UserResponse)
def register(user: UserCreate, db: Session = Depends(get_db)):
    # 检查用户名是否已存在
    db_user = db.query(User).filter(User.username == user.username).first()
    if db_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="用户名已存在"
        )
    
    # 检查邮箱是否已存在
    db_user = db.query(User).filter(User.email == user.email).first()
    if db_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="邮箱已被注册"
        )
    
    # 创建新用户
    hashed_password = User.get_password_hash(user.password)
    db_user = User(
        username=user.username,
        email=user.email,
        hashed_password=hashed_password,
        is_admin=user.is_admin
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

@app.post("/token", response_model=Token)
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
    user = db.query(User).filter(User.username == form_data.username).first()
    if not user or not user.verify_password(form_data.password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    access_token = create_access_token(data={"sub": user.username})
    return {"access_token": access_token, "token_type": "bearer"}



# 数据模型
class Question(BaseModel):
    question: str

class Activity(BaseModel):
    type: str
    content: str
    timestamp: str

class DocumentInfo(BaseModel):
    name: str
    size: int
    uploadTime: str
    type: str

# 存储活动记录
activities: List[Activity] = []

# 原有API路由
@app.post("/api/upload")
async def upload_file(
    file: UploadFile = File(...),
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db),
    prompt: str = Form(None)
):
    if not current_user.is_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Only admin users can upload files"
        )
    print('提示词:',prompt)
    try:
        # 确保上传目录存在
        os.makedirs("data/docs", exist_ok=True)
        
        # 构建文件保存路径
        file_path = os.path.join("data/docs", file.filename)
        print('文件名',file.filename)
        
        # 保存文件
        with open(file_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
            
        # 处理文档
        if process_document(file_path, prompt):
            # 记录活动
            activities.append(Activity(
                type="upload",
                content=f"上传文档: {file.filename}",
                timestamp=datetime.now().isoformat()
            ))
            add_file_record(db, current_user.id, current_user.username, file.filename)
            return {"message": "文档上传成功"}
        else:
            raise HTTPException(status_code=500, detail="文档处理失败")
            
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/query")
async def query(question: Question):
    try:
        answer = query_knowledge(question.question)
        # 记录活动
        activities.append(Activity(
            type="query",
            content=f"查询: {question.question}",
            timestamp=datetime.now().isoformat()
        ))
        return {"answer": answer}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/activities")
async def get_activities():
    return {"activities": activities}

@app.get("/api/graph/visualization/{category}")
async def get_graph_data(category: str):
    try:
        with graph_db.driver.session() as session:
            # 获取所有实体和关系
            result = session.run(f"""
                MATCH (n:{category})
                OPTIONAL MATCH (n)-[r]->(m)
                WITH collect(distinct {{
                    id: id(n),
                    name: n.name,
                    label: coalesce(n.name, '未命名'),
                    type: coalesce(n.type, 'Unknown'),
                    description: coalesce(n.description, '')
                }}) as nodes,
                collect(distinct CASE WHEN r IS NOT NULL
                    THEN {{
                        source: id(startNode(r)),
                        target: id(endNode(r)),
                        type: r.type,
                        label: type(r),
                        description: coalesce(r.description, '')
                    }}
                    ELSE null
                END) as relationships,
                count(distinct n) as nodeCount,
                count(distinct r) as edgeCount
                RETURN nodes, 
                       [rel in relationships WHERE rel IS NOT NULL] as links,
                       nodeCount,
                       edgeCount
            """)
            
            data = result.single()
            if not data:
                return {
                    "code": 200,
                    "data": {
                        "nodes": [],
                        "links": [],
                        "nodeCount": 0,
                        "edgeCount": 0
                    },
                    "message": "暂无图数据"
                }
            
            # 过滤掉无效的节点和关系
            nodes = [node for node in data["nodes"] if node["name"] is not None]
            links = [rel for rel in data["links"] if rel is not None and rel["source"] is not None and rel["target"] is not None]
            print('关系列表',links)
            return {
                "code": 200,
                "data": {
                    "nodes": nodes or [],
                    "links": links or [],
                    "nodeCount": len(nodes),
                    "edgeCount": len(links)
                },
                "message": "获取图数据成功"
            }
            
    except Exception as e:
        print(f"获取图数据时出错: {str(e)}")  # 添加错误日志
        return {
            "code": 500,
            "data": {
                "nodes": [],
                "links": [],
                "nodeCount": 0,
                "edgeCount": 0
            },
            "message": f"获取图数据失败: {str(e)}"
        }

#返回查询节点的关系以及对应节点
@app.get("/api/graph/node/{name}")
async def get_node_and_relation_from_key(name: str):
    try:
        with graph_db.driver.session() as session:
           result = session.run("""
                MATCH (n {name: $name})
                OPTIONAL MATCH (n)-[r]-(relatedNode)
                WITH n, collect(distinct {
                    id: id(relatedNode),
                    name: relatedNode.name,
                    label: coalesce(relatedNode.name, '未命名'),
                    type: coalesce(relatedNode.type, 'Unknown'),
                    description: coalesce(relatedNode.description, '')
                }) as relatedNodes,
                collect(distinct CASE WHEN r IS NOT NULL
                    THEN {
                        source: id(startNode(r)),
                        target: id(endNode(r)),
                        type: type(r),
                        label: type(r),
                        description: coalesce(r.description, '')
                    }
                    ELSE null
                END) as relationships
                RETURN {
                    node: {
                        id: id(n),
                        name: n.name,
                        label: coalesce(n.name, '未命名'),
                        type: coalesce(n.type, 'Unknown'),
                        description: coalesce(n.description, '')
                    },
                    links: [rel in relationships WHERE rel IS NOT NULL],
                    relatedNodes: relatedNodes
                } as result
            """, name=name)
           # 获取单条记录
           record = result.single()
           # 提取具体数据
           data = record["result"]
           if not data:
               return {
                   "code": 200,
                   "data": {
                       "nodes": [],
                       "links": [],
                       "nodeCount": 0,
                       "edgeCount": 0
                   },
                   "message": "暂无图数据"
               }
           print('查询的图数据：', data)  # 打印具体数据，而不是 result 对象

    except Exception as e:
        print(f"查询图数据时出错: {str(e)}")  # 添加错误日志
        return {
            "code": 500,
            "data": {
                "nodes": [],
                "links": [],
                "nodeCount": 0,
                "edgeCount": 0
            },
            "message": f"获取图数据失败: {str(e)}"
        }
    return {
        "code": 200,
        "data": data,
        "message": "搜索成功"
    }

@app.get("/api/stats")
async def get_stats():
    try:
        with graph_db.driver.session() as session:
            # 获取统计信息
            result = session.run("""
                MATCH (n)
                OPTIONAL MATCH (n)-[r:RELATED]->(m)
                WITH count(distinct n) as totalNodes,
                     count(distinct r) as totalRelationships,
                     collect(distinct n.type) as nodeTypes,
                     collect(distinct r.type) as relationTypes
                RETURN {
                    totalNodes: totalNodes,
                    totalRelationships: totalRelationships,
                    nodeTypes: nodeTypes,
                    relationTypes: relationTypes
                } as stats
            """)
            
            data = result.single()["stats"]
            
            return {
                "code": 200,
                "data": {
                    "totalDocuments": 0,  # 这个值需要从文档存储中获取
                    "todayDocuments": 0,  # 这个值需要从文档存储中获取
                    "totalNodes": data["totalNodes"],
                    "totalRelationships": data["totalRelationships"],
                    "nodeTypes": [t for t in data["nodeTypes"] if t],  # 过滤掉空类型
                    "relationTypes": [t for t in data["relationTypes"] if t]  # 过滤掉空类型
                },
                "message": "获取统计信息成功"
            }
            
    except Exception as e:
        return {
            "code": 500,
            "data": None,
            "message": f"获取统计信息失败: {str(e)}"
        }

@app.get("/api/documents")
async def get_documents(page: int = 1, size: int = 10, search: str = ""):
    try:
        # 确保文档目录存在
        docs_dir = "data/docs"
        if not os.path.exists(docs_dir):
            return {"documents": [], "total": 0}
            
        # 获取所有文档
        documents = []
        for filename in os.listdir(docs_dir):
            if filename.endswith(('.docx', '.pdf')):
                if search and search.lower() not in filename.lower():
                    continue
                    
                file_path = os.path.join(docs_dir, filename)
                file_stat = os.stat(file_path)
                
                doc_info = {
                    "name": filename,
                    "size": file_stat.st_size,
                    "uploadTime": datetime.fromtimestamp(file_stat.st_mtime).isoformat(),
                    "type": "PDF" if filename.endswith('.pdf') else "DOCX"
                }
                documents.append(doc_info)
                
        # 计算总数和分页
        total = len(documents)
        start_idx = (page - 1) * size
        end_idx = start_idx + size
        
        # 返回分页后的结果
        return {
            "documents": documents[start_idx:end_idx],
            "total": total
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


# 定义文档存储目录（与代码同级的 data/docs 文件夹）
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data", "docs")


# 文档下载接口
@app.get("/api/documents/download/{filename}")
async def download_document(filename: str):
    """
    下载指定文件名的文档
    :param filename: 文件名（如 "example.docx"）
    :return: 文件响应或错误信息
    """
    file_path = os.path.join(DATA_DIR, filename)
    docs_dir = "data/docs"
    # file_path2 = os.path.join(docs_dir, '吃水果的好处.docx')
    print(f"Attempting to download: {file_path}")

    # 检查文件是否存在
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail=f"文件 {filename} 未找到")

    # 检查路径是否为文件
    if not os.path.isfile(file_path):
        raise HTTPException(status_code=400, detail=f"{filename} 不是一个文件")

    # 返回文件响应，设置下载时的文件名
    return FileResponse(
        path=file_path,
        filename=filename,
        media_type="application/octet-stream"
    )


# 删除文档的接口
@app.delete("/api/documents/{filename}")
async def delete_document(filename: str, db: Session = Depends(get_db)):
    """
    删除指定文件名的文档
    :param filename: 文件名（如 "example.docx"）
    :return: 删除结果或错误信息
    """
    # 构建文件的完整路径
    file_path = os.path.join(DATA_DIR, filename)
    print(f"Attempting to delete: {file_path}")
    # 检查文件是否存在
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail=f"文件 {filename} 未找到")

    # 检查路径是否为文件
    if not os.path.isfile(file_path):
        raise HTTPException(status_code=400, detail=f"{filename} 不是一个文件")

    try:
        # 执行删除操作
        os.remove(file_path)
        delete_file_record(db, filename)
        return {"message": f"文件 {filename} 删除成功"}
    except Exception as e:
        # 处理删除失败的情况
        raise HTTPException(status_code=500, detail=f"删除文件失败: {str(e)}")


@app.get("/api/knowledge/stats")
async def get_knowledge_stats():
    try:
        with graph_db.driver.session() as session:
            result = session.run("""
                MATCH (n)
                OPTIONAL MATCH (n)-[r]->(m)
                WITH count(distinct n) as totalNodes,
                     count(distinct r) as totalRelationships,
                     collect(distinct n.type) as nodeTypes,
                     collect(distinct r.type) as relationTypes,
                     collect(n) as allNodes
                UNWIND allNodes as node
                UNWIND labels(node) as label
                WITH totalNodes, totalRelationships, nodeTypes, relationTypes,
                     label, count(*) as labelCount
                WITH totalNodes, totalRelationships, nodeTypes, relationTypes,
                     collect({label: label, count: labelCount}) as labelStats
                RETURN {
                    totalNodes: totalNodes,
                    totalRelationships: totalRelationships,
                    nodeTypes: nodeTypes,
                    relationTypes: relationTypes,
                    nodesByLabel: labelStats
                } as stats
            """)

            data = result.single()["stats"]

            return {
                "code": 200,
                "data": {
                    "totalNodes": data["totalNodes"],
                    "totalRelationships": data["totalRelationships"],
                    "nodeTypes": [t for t in data["nodeTypes"] if t],
                    "relationTypes": [t for t in data["relationTypes"] if t],
                    "nodesByLabel": data["nodesByLabel"]
                },
                "message": "获取统计信息成功"
            }

    except Exception as e:
        return {
            "code": 500,
            "data": None,
            "message": f"获取统计信息失败: {str(e)}"
        }

@app.post("/api/knowledge/query")
async def knowledge_query(question: Question,db: Session = Depends(get_db),current_user: User = Depends(get_current_user)):
    try:
        category = get_question_category(question.question)
        print('用户',current_user.id)
        add_search_record(
            db=db,
            user_id=current_user.id,
            search_content=question.question,
            category=category
        )
        answer = query_knowledge(question.question)
        activities.append(Activity(
            type="query",
            content=f"查询: {question.question}",
            timestamp=datetime.now().isoformat()
        ))
        return {
            "code": 200,
            "data": answer,
            "message": "查询成功"
        }
    except Exception as e:
        return {
            "code": 500,
            "data": None,
            "message": f"查询失败: {str(e)}"
        }



# 启动时加载知识库
@app.on_event("startup")
async def startup_event():
    load_knowledge()

if __name__ == "__main__":
    import uvicorn
    print("正在启动服务器...")
    try:
        uvicorn.run(
            "api:app",
            host="0.0.0.0",
            port=8000,
            reload=True,
            log_level="info"
        )
    except Exception as e:
        print(f"启动服务器时出错: {str(e)}")
        raise 